跨域
域名、协议或者端口有一个不同就是跨域,浏览器请求资源会失败。
限制范围:
Cookie、LocalStorage和IndexDB无法获取。- 无法获取和操作
DOM。 - 不能发送
ajax请求。我们要注意,ajax只适合同源的通信。
但是 HTML 中的 script 、 img 、 link,这三个标签的 src/href 可以加载其他域的资源,不受同源策略限制。
出于什么安全考虑才会引入跨域
主要是用来防止 CSRF 攻击的。CSRF 攻击是利用用户的登录态发起恶意请求。没有同源策略的情况下,A 网站可以被任意其他来源的 ajax 访问到内容。如果你当前 A 网站还存在登录态,那么对方就可以通过 ajax 获得你的任何信息。当然跨域并不能完全阻止 CSRF。
跨域通信的几种方式
开发环境
- webpack-dev-server 的 proxy. 本质上与代码工具的原理差不多。 http-proxy-middleware
- 代理工具,比如 whistle
线上环境
JSONPpostMessageCORSWebSocketHashurl 中 ## 号后面的内容, hash 修改页面不刷新,利用 window.onhashChange 获取修改的 hash- 通过修改
document.domain来跨子域 - 使用
window.name来进行跨域
JSONP 原理
JSONP = JSON + Padding. 原理很简单,因为 <script> 标签没有跨域限制。JSONP 使用简单且兼容性不错,但是只限于 get 请求。前端需要常见一个回调函数,拼接到动态创建的 script 标签的 query 中,服务端接收到请求之后将数据填充到回调函数中。
自身需要提供一个回调函数用于处理传入的数据,一般会通过 query 参数告知请求的地址,script 请求的内容实际会被执行,将域外的数据传入回调函数中完成填充。
注意点:
- callback 函数要绑定在 window 对象上
- 服务端返回数据有特定格式要求:
callback 函数名+'('+JSON.stringify(返回数据) +')' - 不支持 post,因为 js 标签本身就是一个 get 请求
后端的处理
router.get("/getinfo", function (req, res, next) {
var _callback = req.query.callback;
var _data = { email: "example@163.com", name: "jaxu" };
if (_callback) {
res.type("text/javascript");
res.send(_callback + "(" + JSON.stringify(_data) + ")");
} else {
res.json(_data);
}
});
PostMessage
场景:窗口 A (http:A.com)向跨域的窗口 B (http:B.com)发送信息。步骤如下
- 在
A窗口中操作如下:向B窗口发送数据:
// 窗口A(http:A.com)向跨域的窗口B(http:B.com)发送信息
Bwindow.postMessage("data", "http://B.com"); //这里强调的是B窗口里的window对象
- onmessage 事件。在
B窗口中操作如下:
// 在窗口B中监听 message 事件
Awindow.addEventListener(
"message",
function (event) {
//这里强调的是A窗口里的window对象
console.log(event.origin); //获取 :url。这里指:http://A.com
console.log(event.source); //获取:A window对象
console.log(event.data); //获取传过来的数据
},
false
);
CORS 跨域资源共享
服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。 如果浏览器检测到相应的设置,就可以允许ajax进行跨域的访问
CORS需要浏览器和后端同时支持- 浏览器会自动进行
CORS通信,实现 CORS 通信的关键是后端。只要后端实现了CORS,就实现了跨域。 - 服务端设置
Access-Control-Allow-Origin就可以开启CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源
后台响应 options 请求类型
location ~/cgi {
if ($request_method = 'OPTIONS' ) {
add_header 'Access-Control-Allow-Origin' '*';
add_header Access-Control-Allow-Methods "GET, OPTIONS, POST";
add_header 'Access-Control-Allow-Credentials' "true";
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
#add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
proxy_pass http://127.0.0.1:80;
add_header Access-Control-Allow-Origin '*';
add_header 'Access-Control-Allow-Credentials' "true";
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
}
主要的字段:
- Access-Control-Allow-Origin
- Access-Control-Allow-Headers 实际请求将携带的自定义请求首部字段
- Access-Control-Allow-Methods
- Access-Control-Allow-Credentials
- Access-Control-Max-Age 用来指定本次预检请求的有效期,单位为秒。
注意事项
跨域请求头具体的值
- 跨域带 cookie 的时候 不能设置
*通配符,而是要写具体的域名 - 有自定义的 header 部分需要做一定的配置
Options 预检请求
fetch 发送 2 次请求的原因
fetch 发送 post 请求的时候,总是发送 2 次,第一次状态码是 204,第二次才成功?
原因很简单,是一个探测性的方法,客户端通过该方法可以在不访问服务器上实际资源的情况下就知道处理该资源的最优方式。因为你用 fetch 的 post 请求的时候,导致 fetch 第一次发送了一个 Options 请求,询问服务器是否支持修改的请求头,如果服务器支持,则在第二次中发送真正的请求
设置响应状态码为 204 是为了告知客户端表示该响应成功了,但是该响应并没有返回任何响应体,如果状态码为 200,还得携带多余的响应体,在这种场景下是完全多余的,只会浪费流量。
简单请求
以 ajax 为例,当满足以下条件时,会触发简单请求
- 使用下列方法之一:
GETHEADPOST
Content-Type的值仅限于下列三者之一:text/plainmultipart/form-dataapplication/x-www-form-urlencoded
对于简单请求,浏览器会直接发送 CORS 请求,具体说来就是在 header 中加入 origin 请求头字段。同样,在响应头中,返回服务器设置的相关 CORS 头部字段,Access-Control-Allow-Origin 字段为允许跨域请求的源。
复杂请求
对于复杂请求来说,首先会发起一个预检请求,该请求是 option 方法的,通过该请求来知道服务端是否允许跨域请求。
预检请求发生的条件:
使用了下面任一 HTTP 方法:
- PUT
- DELETE
- CONNECT
- OPTIONS
- TRACE
- PATCH
Content-Type 的值不属于下列之一:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
# 配置如果是OPTIONS方法直接返回 204 状态
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PATCH, DELETE, PUT, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type, Access-Control-Expose-Headers, Token, Authorization';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
axios 的 cookie 跨域以及相关配置
// 携带 cookie
axios.defaults.withCredentials = true;
axios 默认是发送请求的时候不会带上 cookie 的,需要通过设置 withCredentials: true 来解决。 这个时候需要注意需要后端配合设置:
- header 信息 Access-Control-Allow-Credentials:true
- Access-Control-Allow-Origin 不可以为
*,因为*会和 Access-Control-Allow-Credentials:true 冲突,需配置指定的地址
开发的环境下我们前端可以自己配置个 proxy 代理就能跨域了,真正的生产环境下还需要后端的配合的。
{
"proxyTable": {
"/api": {
"target": "http://10.1.5.11:8080/",
"changeOrigin": true, //跨域
"pathRewrite": {
"^/api": "/"
}
}
}
}